
/**
 * Java引用变量有两个类型：一个是编译时类型，一个是运行时类型。
 * 编译时类型由声明该变量时使用的类型决定，运行时类型由实际赋给该变量的对象决定。
 * 如果编译时类型和运行时类型不一致，就可能出现所谓的多态（Polymorphism）。
 */
class BaseClass {
    public int book = 6;

    public void base() {
        System.out.println("父类的普通方法");
    }

    public void test() {
        System.out.println("父类被覆盖的方法");
    }
}

class SubClass extends BaseClass {
    public String book = "Java多态机制";

    public void test() {
        System.out.println("子类中的覆盖父类的方法");
    }

    public void sub() {
        System.out.println("子类的普通方法");
    }

    public static void main() {
        // 下面编译时类型和运行时类型完全一致，因此不存在多态
        BaseClass bc = new BaseClass();
        System.out.println(bc.book);
        bc.base();
        bc.test();
        // 下面编译时类型和运行时类型完全一致，因此不存在多态
        SubClass sc = new SubClass();
        System.out.println(sc.book);
        sc.base();
        sc.sub();

        // 下面编译时类型和运行时类型不一样，发生多态
        // 因为子类其实是一种特殊的父类，因此Java允许把一个子类对象直接赋给一个父类引用变量，
        // 无须任何类型转换，或者被称为向上转型（upcasting），向上转型由系统自动完成。
        BaseClass polymorphicBc = new SubClass();
        System.out.println(polymorphicBc.book);
        // 下面调用将执行从父类继承到的base方法
        polymorphicBc.base();
        // 下面调用将执行当前类的test方法
        polymorphicBc.test();
        // 因为polymorphicBc的编译时类型是BaseClass，
        // BaseClass类没有提供sub()方法，所以下面代码编译时会出现错误
//        polymorphicBc.sub();

        /*
         * 这个polymorphicBc引用变量的编译时类型是BaseClass，而运行时类型是SubClass，
         * 当运行时调用该引用变量的方法时，其方法行为总是表现出子类方法的行为特征，而不是父类方法的行为特征，
         * 这就可能出现：相同类型的变量、调用同一个方法时呈现出多种不同的行为特征，这就是多态。
         * 与方法不同的是，对象的实例变量则不具备多态性。
         * 注意：引用变量在编译阶段只能调用其编译时类型所具有的方法，但运行时则执行它运行时类型所具有的方法。
         * 注意：通过引用变量来访问其包含的实例变量时，系统总是试图访问它编译时类型所定义的成员变量，而不是它运行时类型所定义的成员变量。
         */
    }
}

/**
 * 引用变量的强制类型转换
 * 类型转换运算符是小括号，类型转换运算符的用法是：(type)variable，这种用法可以将variable变量转换成一个type类型的变量。
 * 类型转换运算符可以将一个基本类型变量转换成另一个类型。
 * 除此之外，这个类型转换运算符还可以将一个引用类型变量转换成其子类类型。
 * 这种强制类型转换不是万能的，当进行强制类型转换时需要注意：
 * ➢ 基本类型之间的转换只能在数值类型之间进行，这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。
 * ➢ 引用类型之间的转换只能在具有继承关系的两个类型之间进行，如果是两个没有任何继承关系的类型，则无法进行类型转换，否则编译时就会出现错误。
 * 如果试图把一个父类实例转换成子类类型，则这个对象必须实际上是子类实例才行（即编译时类型为父类类型，而运行时类型是子类类型），
 * 否则将在运行时引发ClassCastException异常。
 */
class ConversionTest {
    public static void main() {
        double d = 13.4;
        long l = (long) d;
        System.out.println(l);

        int in = 5;
        // 编译错误
//        boolean b = (boolean)in;

        // obj变量的编译时类型为Object，Object与String存在继承关系，可以强制类型转换
        // 而obj变量的实际类型也是String，因此运行时也可以通过
        Object obj = "Hello";
        String objStr = (String) obj;
        System.out.println(objStr);

        // 定义一个objPri变量，编译时类型为Object，实际类型为Integer
        // 存在继承关系，可以进行强制类型转换
        Object objPri = Integer.valueOf(5);
        // objPri实际类型为Integer，运行时引发ClassCastException异常
//        String str = (String)objPri;

        // 考虑到进行强制类型转换时可能出现异常，因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。
        if (objPri instanceof String) {
            String str = (String) objPri;
        }
        // 在进行强制类型转换之前，先用instanceof运算符判断是否可以成功转换，从而避免出现ClassCastException异常，这样可以保证程序更加健壮。
    }
}

/**
 * 注意：当把子类对象赋给父类引用变量时，被称为向上转型（upcasting），这种转型总是可以成功的，这也从另一个侧面证实了子类是一种特殊的父类。
 * 这种转型只是表明这个引用变量的编译时类型是父类，但实际执行它的方法时，依然表现出子类对象的行为方式。
 * 但把一个父类对象赋给子类引用变量时，就需要进行强制类型转换，而且还可能在运行时产生ClassCastException异常，使用instanceof运算符可以让强制类型转换更安全。
 */

/**
 * instanceof运算符
 * <p>
 * instanceof运算符的前一个操作数通常是一个引用类型变量，后一个操作数通常是一个类（也可以是接口，可以把接口理解成一种特殊的类），
 * 它用于判断前面的对象是否是后面的类，或者其子类、实现类的实例。如果是，则返回true，否则返回false。
 * <p>
 * 在使用instanceof运算符时需要注意：instanceof运算符前面操作数的编译时类型要么与后面的类相同，要么与后面的类具有父子继承关系，否则会引起编译错误。
 */
class InstanceofTest {
    public static void main() {
        Object hello = "Hello"; // 编译时类型Object，实际类型String
        System.out.println("字符串是否是Object类的实例：" + (hello instanceof Object));
        System.out.println("字符串是否是String类的实例：" + (hello instanceof String));
        // Math与Object类存在继承关系，可以进行instanceof运算，返回false
        System.out.println("字符串是否是Math类的实例：" + (hello instanceof Math));
        // String实现了Comparable接口，返回true
        System.out.println("字符串是否是Comparable接口的实例：" + (hello instanceof Comparable));
        String a = "Hello";
        // String类和Math类没有继承关系，所以下面代码编译无法通过
//        System.out.println("字符串是否是Math类的实例："+ (a instanceof Math));
    }
}

public class Polymorphism {
    public static void main(String[] args) {
        SubClass.main();
        ConversionTest.main();
        InstanceofTest.main();
    }
}
